package graphdao

import (
	"fmt"
	"power/core/dao/table"
	"power/core/service/common"
	"power/core/service/graph"

	"strconv"

	"github.com/RocksonZeta/wrap/redised"
	"github.com/RocksonZeta/wrap/utils/cutil"
	"github.com/RocksonZeta/wrap/utils/sqlutil"
	"github.com/RocksonZeta/wrap/utils/sutil"
	"github.com/RocksonZeta/wrap/utils/timeutil"
)

const tableGraphElement = "GraphElement"

type GraphElements struct {
	*redised.RedisedMysql
}

func (s GraphElements) redisKey(id int) string {
	return tableGraphElement + "/" + strconv.Itoa(id)
}

func (s GraphElements) ClearCache(id int) {
	o := s.Get(id)
	s.Redis.Del(s.redisKey(id))
	//clear list by cache
	s.Redis.Del(s.KVFn(tableGraph, sutil.Kv2Map("graphId", o.GraphId)))
}

type GraphElementsQueryParam struct {
	common.Page
	Name string
}

//Query 根据条件查询
func (s GraphElements) Query(param GraphElementsQueryParam) ([]table.GraphElement, int64) {
	wsql := " from GraphElement where 1=1"
	if param.Name != "" {
		param.Name = "%" + param.Name + "%"
		wsql += " and name like :Name"
	}
	ssql := "select *" + wsql + " order by ct desc" + param.Limit()
	csql := "select count(*)" + wsql
	var r []table.GraphElement
	s.Mysql.Select(&r, ssql, param)
	return r, s.Mysql.SelectInt(csql, param)
}

func (s GraphElements) Get(id int) table.GraphElement {
	var r table.GraphElement
	s.RedisedMysql.Get(&r, tableGraphElement, "Id", id)
	return r
}
func (s GraphElements) Get_(id int) graph.GraphElement {
	var r graph.GraphElement
	e := s.Get(id)
	r.GraphElement = e
	r.Props = graph.UnmarshalProps(e.Props)
	return r
}

func (s GraphElements) List(ids []int) []table.GraphElement {
	var r []table.GraphElement
	s.RedisedMysql.List(&r, tableGraphElement, "Id", ids)
	return r
}

// shoud clear cache
func (s GraphElements) ListByGraphId(graphId int) []table.GraphElement {
	var r []table.GraphElement
	s.RedisedMysql.ListByKvs(&r, tableGraphElement, "Id", sutil.M{"graphId": graphId})
	return r
}
func (s GraphElements) ListByGraphId_(graphId int) []graph.GraphElement {
	l := s.ListByGraphId(graphId)
	r := make([]graph.GraphElement, len(l))
	for i, v := range l {
		r[i].GraphElement = v
		r[i].Props = graph.UnmarshalProps(v.Props)
	}
	return r
}

type GraphElementsAddParam_ struct {
	// Type    int
	Name    string
	ProtoId int
	GraphId int
	Props   graph.Props
}

func (s GraphElements) Add_(param GraphElementsAddParam_) table.GraphElement {
	return s.Add(GraphElementsAddParam{
		// Type:    param.Type,
		Name:    param.Name,
		ProtoId: param.ProtoId,
		GraphId: param.GraphId,
		Props:   param.Props.Marshal(),
	})
}

type GraphElementsAddParam struct {
	// Type    int
	Name    string
	ProtoId int
	GraphId int
	Props   string
}

func (s GraphElements) Add(param GraphElementsAddParam) table.GraphElement {
	var t table.GraphElement
	sutil.Copy(param, &t)
	p := GraphElementProtos{RedisedMysql: s.RedisedMysql}.Get(param.ProtoId)
	fmt.Println(graph.Marshal(p))
	t.Type = p.Type
	t.Ct = timeutil.Now()
	s.Mysql.AddTable(t)
	s.Mysql.Insert(&t)
	s.ClearCache(t.Id)
	return t
}

type GraphElementsUpdateParam struct {
	Id   int
	Name string
}

func (s GraphElements) Update(param GraphElementsUpdateParam) {
	s.Mysql.Patch(tableGraphElement, "Id", param)
	s.ClearCache(param.Id)
}
func (s GraphElements) UpdateProps(id int, props string) {
	s.Mysql.Patch(tableGraphElement, "id", sutil.Kv2Map("id", id, "props", props))
	s.ClearCache(id)
}
func (s GraphElements) UpdateData(id int, data string) {
	s.Mysql.Patch(tableGraphElement, "id", sutil.Kv2Map("id", id, "data", data))
	s.ClearCache(id)

}

func (s GraphElements) Delete(id int) {
	log.Debug().M("GraphElements").Func("Delete").Int("id", id).Send()
	s.Mysql.Delete(tableGraphElement, "Id", id)
	s.ClearCache(id)
}
func (s GraphElements) Deletes(ids []int) {
	log.Debug().M("GraphElements").Func("Deletes").Ints("ids", ids).Send()
	if len(ids) == 0 {
		return
	}
	o := s.Get(ids[0])
	s.Mysql.Exec("delete from " + tableGraphElement + " where id in (" + sqlutil.JoinInts(ids) + ")")
	s.Redis.Del(cutil.MapIds(ids, s.redisKey)...)
	s.Redis.Del(s.KVFn(tableGraph, sutil.Kv2Map("graphId", o.GraphId)))
}

func (s GraphElements) AsMap(elements []graph.GraphElement) map[int]graph.GraphElement {
	r := make(map[int]graph.GraphElement, len(elements))
	for _, v := range elements {
		r[v.Id] = v
	}
	return r
}
func (s GraphElements) IsParent(elementId, parentId int, elementMap map[int]graph.GraphElement) bool {
	cur, ok := elementMap[elementId]
	if !ok || elementId <= 0 || cur.Props.GetParent() <= 0 {
		return false
	}
	if cur.Props.GetParent() > 0 && cur.Props.GetParent() == parentId {
		return true
	}
	return s.IsParent(cur.Props.GetParent(), parentId, elementMap)
}

func SetInt(ints []int) map[int]bool {
	r := make(map[int]bool, len(ints))
	for _, v := range ints {
		r[v] = true
	}
	return r
}

func (s GraphElements) DeleteRelated(id int) {
	log.Debug().M("GraphElements").Func("DeleteRelated").Int("id", id).Send()
	o := s.Get_(id)
	s.Mysql.Delete(tableGraphElement, "Id", id)
	if o.Type != graph.ElementTypeEdge {
		all := s.ListByGraphId_(o.GraphId)
		var deleteEdges []int //需要删除的相关元素
		for _, v := range all {
			if v.Type == graph.ElementTypeEdge && (v.Props.GetFrom() == id || v.Props.GetTo() == id) {
				deleteEdges = append(deleteEdges, v.Id)
			}
			elementMap := s.AsMap(all)
			if o.Type == graph.ElementTypeGroup { //组内的节点全部删除
				var deleteNodes []int
				if s.IsParent(v.Id, id, elementMap) {
					deleteNodes = append(deleteNodes, v.Id)
				}
				deleteNodeMap := SetInt(deleteNodes)
				if len(deleteNodeMap) > 0 {
					for _, v1 := range all {
						if v1.Type == graph.ElementTypeEdge && (deleteNodeMap[v1.Props.GetFrom()] || deleteNodeMap[v1.Props.GetTo()]) {
							deleteEdges = append(deleteEdges, v1.Id)
						}
					}
				}
				deleteEdges = append(deleteEdges, deleteNodes...)
			}
		}
		log.Debug().M("GraphElements").Func("Delete").Ints("deleteEdges", deleteEdges).Send()
		s.Mysql.Deletes(tableGraphElement, "Id", deleteEdges)
	}
	s.ClearCache(id)
}
